﻿using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System;

using xEngine.Maths;
using xEngine.DataTypes;
using xEngine.Rendering.Effects;

namespace xEngine.Actors
{
    public class Actor
    {
        #region Member Variables

        private string _name;

        protected List<Actor> _containedActors;
        protected Actor _containingActor;

        #endregion

        #region Properties

        public Transformation Transformation { get; private set; }
        public string Name
        {
            get
            {
                if(string.IsNullOrEmpty(_name))
                    return GetType().Name;
                else
                    return _name;
            }
            set { _name = value; }
        }
        public bool Collidable { get; set; }
        public bool Enabled { get; set; }
        public bool Visible { get; set; }
        public XEffect DefaultShader { get; protected set; }

        public BoundingShape BoundingShape { get; protected set; }

        #endregion

        #region Constructors

        public Actor()
        {
            Enabled = Visible = true;
            _containedActors = new List<Actor>();
            Transformation = new Transformation();
            Transformation.OnMatrixInvalidated += new EventHandler(delegate { OnTransformMatrixUpdate(); });
            BoundingShape = new BoundingShape();
        }

        #endregion

        #region Functions

        public virtual void Rotate(float x, float y)
        {
            Vector3 r = Transformation.Rotation;

            if (x != 0)
                r.X += x;

            if (y != 0)
                r.Y += y;

            Transformation.Rotation = r;
        }
        public virtual void Move(Vector3 moveVector)
        {
            Transformation.Position += Vector3.Transform(moveVector, Transformation.RotationM);
        }

        public virtual void LookAt(Actor target)
        {
            LookAt(target.Transformation.Position);
        }
        public virtual void LookAt(Vector3 target)
        {
            Transformation.Direction = Vector3.Normalize(target - Transformation.Position);
        }

        public virtual void OnRemove() { }
        public virtual void OnAdd() { }

        public virtual void Render() { }
        public virtual void Update(GameTime time) { }

        public virtual void Dispose()
        {
            if (DefaultShader != null)
                DefaultShader.Dispose();
        }

        public virtual void AddActor(Actor actor)
        {
            if (actor != null)
            {
                actor._containingActor = this;
                _containedActors.Add(actor);
            }
        }
        public virtual void RemoveActor(Actor actor)
        {
            if(actor != null)
            {
                _containedActors.Remove(actor);
            }
        }
        public virtual void ClearActors()
        {
            _containedActors.Clear();
        }

        public virtual IntersectionResult Collides(Actor actor)
        {
            return Collides(actor, false);
        }
        public virtual IntersectionResult Collides(Actor actor, bool forceCheck)
        {
            return ((Collidable && actor.Collidable) || forceCheck) ? BoundingShape.Intersects(actor.BoundingShape) : false;
        }
        public virtual IntersectionResult Collides(Ray ray)
        {
            return Collides(ray, false);
        }
        public virtual IntersectionResult Collides(Ray ray, bool forceCheck)
        {
            return (Collidable || forceCheck) ? BoundingShape.Intersects(ray) : false;
        }

        protected virtual void OnTransformMatrixUpdate() { }

        #endregion

        #region Enums
        #endregion
    }

    public class Transformation
    {
        private Vector3 _rotationV;
        private Vector3 _positionV;
        private Vector3 _scaleV;

        private Matrix _rotationM;

        private Matrix _localMatrix;
        private Matrix _matrix;
        private bool _localMatrixInvalid;
        private bool _worldMatrixInvalid;

        private bool WorldMatrixInvalid
        {
            get { return _worldMatrixInvalid; }
            set
            {
                _worldMatrixInvalid = value;
                if (value && OnMatrixInvalidated != null)
                    OnMatrixInvalidated(this, EventArgs.Empty);
            }
        }

        public Matrix RotationM
        {
            get { return _rotationM; }
            private set { _rotationM = value; }
        }
        public Matrix PositionM { get; private set; }
        public Matrix ScaleM { get; private set; }
        public Vector3 Position
        {
            get { return _positionV; }
            set
            {
                _positionV = value;
                PositionM = Matrix.CreateTranslation(_positionV);
                WorldMatrixInvalid = true;
            }
        }
        public Vector3 Rotation
        {
            get { return _rotationV; }
            set
            {
                _rotationV = value;
                RotationM = Matrix.Identity;

                if (_rotationV.X != 0)
                    RotationM *= Matrix.CreateRotationX(_rotationV.X);

                if (_rotationV.Y != 0)
                    RotationM *= Matrix.CreateRotationY(_rotationV.Y);

                if (_rotationV.Z != 0)
                    RotationM *= Matrix.CreateRotationZ(_rotationV.Z);

                WorldMatrixInvalid = true;
            }
        }
        public Vector3 Scale
        {
            get { return _scaleV; }
            set
            {
                _scaleV = value;
                ScaleM = Matrix.CreateScale(_scaleV);
                WorldMatrixInvalid = true;
            }
        }

        public Matrix LocalOnlyMatrix
        {
            get { return _localMatrix; }
            set
            {
                _localMatrix = value;
                _localMatrixInvalid = true;
                if (OnMatrixInvalidated != null)
                    OnMatrixInvalidated(this, EventArgs.Empty);
            }
        }
        public Matrix WorldOnlyMatrix { get; private set; }
        public Matrix Matrix
        {
            get
            {
                if (WorldMatrixInvalid || _localMatrixInvalid)
                {
                    _matrix = LocalOnlyMatrix * (WorldOnlyMatrix = ScaleM * RotationM * PositionM);
                    WorldMatrixInvalid = _localMatrixInvalid = false;
                }

                return _matrix;
            }
        }

        public Vector3 Direction
        {
            get { return RotationM.Forward; }
            set { Rotation = XMath.AnglesFromForward(value); }
        }

        public Transformation()
        {
            _localMatrix = _matrix = RotationM = ScaleM = PositionM = Matrix.Identity;
            _scaleV = Vector3.One;
        }

        public event EventHandler OnMatrixInvalidated;
    }
}
